"""
布林带（Bollinger Bands）交易策略基于统计学原理，通过计算股价的标准差与移动平均线，动态描绘出股价波动的通道区间。
策略核心由三条线构成：中间为中轨线（通常是 20 日移动平均线），代表股价的中期趋势；
上下两条分别为上轨线和下轨线，通过在中轨线基础上加减一定倍数的标准差（默认 2 倍）生成，反映股价波动的极端水平。
其交易逻辑在于捕捉股价的超买与超卖状态：当股价触及下轨线时，市场可能处于超卖状态，暗示股价短期过度下跌，反弹可能性增加，构成潜在买入信号；
若此时成交量显著放大（如超过 5 日均量的 120%），更能佐证市场情绪反转，强化买入信号。
反之，当股价触及上轨线，表明市场进入超买区域，股价短期上涨过度，存在回调风险，触发卖出信号；
若伴随成交量激增，则进一步确认市场顶部信号。
此外，策略还可通过股价与中轨线的位置关系判断趋势：股价位于中轨线上方，维持 “持有” 状态，表明多头趋势占优；
股价跌破中轨线，则转为 “观望”，提示空头趋势主导。
布林带策略的优势在于其动态适应性，能够随市场波动自动调整通道宽度，有效过滤噪声并捕捉趋势变化。
同时，结合成交量等辅助指标，可显著提升信号的可靠性，减少误判。
该策略广泛适用于股票、期货、外汇等多类交易市场，尤其在震荡行情中表现出色，帮助投资者把握高概率交易机会，实现科学、量化的投资决策 。
"""

import pandas as pd
from sqlalchemy import create_engine
import matplotlib.pyplot as plt
from datetime import datetime
import numpy as np

# 创建数据库连接引擎
engine = create_engine('mysql+pymysql://root:@127.0.0.1:3306/stocks?charset=utf8mb4&use_unicode=1')

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False


def generate_bollinger_signals(window=20, num_std=2):
    """
    为所有股票生成布林带策略信号并以表格图表展示

    参数可调:
        window: 计算布林带的时间窗口(默认20日)
        num_std: 标准差倍数(默认2)
    """
    # 获取所有股票代码和名称
    query = "SELECT ts_code, name FROM stocks_info"
    stocks = pd.read_sql(query, con=engine)

    data = []
    for _, stock in stocks.iterrows():
        ts_code = stock['ts_code']
        name = stock['name']

        try:
            # 获取足够数据(窗口大小+10天缓冲)
            query = f"""
                SELECT trade_date, close, vol 
                FROM stocks_daily 
                WHERE ts_code = '{ts_code}' 
                ORDER BY trade_date DESC 
                LIMIT {window + 10}
            """
            df = pd.read_sql(query, con=engine)

            if len(df) < window:
                signal = f"数据不足(需要{window}天)"
            else:
                # 按日期升序排列计算
                df = df.sort_values('trade_date')

                # 计算布林带指标
                df['middle'] = df['close'].rolling(window=window).mean()
                df['std'] = df['close'].rolling(window=window).std()
                df['upper'] = df['middle'] + (df['std'] * num_std)
                df['lower'] = df['middle'] - (df['std'] * num_std)

                # 计算成交量变化(5日均量)
                df['vol_ma5'] = df['vol'].rolling(window=5).mean()

                # 最新数据
                latest = df.iloc[-1]
                prev = df.iloc[-2]

                # 生成信号
                if latest['close'] <= latest['lower']:
                    # 价格触及下轨且成交量放大(超过5日均量20%)
                    if latest['vol'] > latest['vol_ma5'] * 1.2:
                        signal = "强烈买入信号(触及下轨+放量)"
                    else:
                        signal = "买入信号(触及下轨)"
                elif latest['close'] >= latest['upper']:
                    # 价格触及上轨且成交量放大(超过5日均量20%)
                    if latest['vol'] > latest['vol_ma5'] * 1.2:
                        signal = "强烈卖出信号(触及上轨+放量)"
                    else:
                        signal = "卖出信号(触及上轨)"
                elif latest['close'] > latest['middle']:
                    signal = "持有(中轨上方)"
                elif latest['close'] < latest['middle']:
                    signal = "观望(中轨下方)"
                else:
                    signal = "中性(中轨附近)"

            data.append([name, ts_code, signal])

        except Exception as e:
            data.append([name, ts_code, f"处理错误 - {str(e)}"])

    df_result = pd.DataFrame(data, columns=['股票名称', '股票代码', '信号'])

    # 创建表格图表
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.axis('off')

    # 创建表格
    table = ax.table(
        cellText=df_result.values,
        colLabels=df_result.columns,
        loc='center',
        cellLoc='center'
    )

    # 设置表格样式
    table.auto_set_font_size(False)
    table.set_fontsize(10)
    table.scale(1, 1.5)

    # 设置列宽
    table.auto_set_column_width([0, 1, 2])

    # 设置表格内容居中
    for key, cell in table.get_celld().items():
        cell.set_text_props(ha='center', va='center')
        cell.set_edgecolor('lightgray')  # 添加细边框

    # 设置表头样式
    for i in range(len(df_result.columns)):
        cell = table[(0, i)]
        cell.set_facecolor('#40466e')
        cell.get_text().set_color('white')
        cell.set_fontsize(12)

    # 遍历表格单元格，根据信号设置背景颜色
    for i in range(1, len(df_result) + 1):
        signal = df_result.iloc[i - 1]['信号']
        if '买入' in signal:
            color = '#ff6b6b'  # 红色
            table[(i, 0)].set_facecolor(color)
            table[(i, 1)].set_facecolor(color)
            table[(i, 2)].set_facecolor(color)
            for j in range(3):
                table[(i, j)].get_text().set_color('white')
        elif '卖出' in signal:
            color = '#51cf66'  # 绿色
            table[(i, 0)].set_facecolor(color)
            table[(i, 1)].set_facecolor(color)
            table[(i, 2)].set_facecolor(color)
            for j in range(3):
                table[(i, j)].get_text().set_color('white')
        elif '观望' in signal:
            color = '#339af0'  # 蓝色
            table[(i, 0)].set_facecolor(color)
            table[(i, 1)].set_facecolor(color)
            table[(i, 2)].set_facecolor(color)
            for j in range(3):
                table[(i, j)].get_text().set_color('white')

    # 计算表格占据的垂直空间比例
    table_rows = len(df_result) + 1  # 包括表头行
    fig_height = fig.get_size_inches()[1]  # 获取图形高度(英寸)
    row_height = 0.4  # 每行的近似高度(英寸)

    # 计算表格占据的总高度(英寸转换为相对高度)
    table_total_height = (table_rows * row_height) / (fig_height * 72)  # 72 points per inch

    # 动态调整时间戳位置
    if table_total_height > 0.85:
        timestamp_y = 0.01  # 表格很高时放在最底部
    elif table_total_height > 0.6:
        timestamp_y = 0.02  # 表格较高时
    else:
        timestamp_y = 0.03  # 表格较小时

    # 添加时间戳
    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    time_text = ax.text(
        0.98,  # x位置(0.98表示靠近右侧)
        timestamp_y,  # y位置(动态调整)
        f"生成时间: {current_time}",
        transform=ax.transAxes,  # 使用相对坐标
        ha='right',  # 右对齐
        va='bottom',  # 底部对齐
        fontsize=10,
        bbox=dict(
            facecolor='white',
            alpha=0.8,
            edgecolor='gray',
            boxstyle='round,pad=0.3'
        )
    )
    # 添加标题
    plt.title(
        f'股票布林带策略信号汇总({window}日, {num_std}倍标准差)',
        pad=20,  # 增加标题与表格的间距
        fontsize=14,
        fontweight='bold'
    )
    # 调整布局
    plt.tight_layout()
    plt.subplots_adjust(
        top=0.9,  # 为标题留出空间
        bottom=0.1  # 为时间戳留出空间
    )

    plt.show()

# 生成并展示信号图表
generate_bollinger_signals()